/**
 *  @file HttpServer.cpp
 */
#include "HttpServer.h"
#include "../SocketFactory.h"
#include "../../text/CFString.h"
#include "../../convert/Convert.h"
#include "../../collections/KeyValueListT.h"
#include "../SocketException.h"
#include "HttpException.h"
#include "HttpServerEndPoint.h"

using namespace cppflib::collections;

namespace cppflib
{

namespace net
{

namespace http
{

/**
 *  Server cancel context
 */
class ServerCancelContext : public ICancelContext
{

private:
   Socket *pWaitSock;   ///< the extra UDP socket
   
   void ReleaseBeforeThrowing()
   {
      cppmempool::Release(pWaitSock);
   }

public:
   ServerCancelContext()
   {
      pWaitSock = new Socket();
      cppmempool::Retain(pWaitSock);
      if (!pWaitSock->Create(CFString(Socket::LOCALHOST), 0, Socket::Dgram))
         THROW_CTOR_EXCEP(SocketException, _S("Create waiting socket for HttpServer failed"));
   }

   virtual ~ServerCancelContext()
   {
      ReleaseBeforeThrowing();
   }

   Socket * GetInternalSock()
   {
      return pWaitSock;
   }

   /**
    *  Cancel waiting by sending some bytes to the waiting socket such that
    *  the "select" call can return immediately
    */
   virtual bool CancelWaiting()
   {
      CFString addr;
      u16_t port = 0;
      pWaitSock->GetLocalAddress(addr, port);  // get port of waiting socket

      CFString localHostAddr(Socket::LOCALHOST);
      Socket tempSocket;
      if (!tempSocket.Create(localHostAddr, 0, Socket::Dgram)) // make a temp socket
         return false;

      u8_t tempData[4] = {0};
      tempSocket.SendTo(tempData, sizeof(tempData), 0, localHostAddr, port); // send data to waiting socket
      tempSocket.Close();
      return true;
   }
};


/********************** HttpServer **************************/

HttpServer::HttpServer(void)
{
}

/**
 *  @param [in] pParams -- parameters to setup this server
 */
HttpServer::HttpServer(HttpParams * pParams)
{
   this->pParams = pParams;
   cppmempool::Retain(pParams);

   this->pSockList = new ListCppObjT<Socket>();
   cppmempool::Retain(this->pSockList);

   this->pSockArray = NULL;

   try {
      this->pServerCancelContext = new ServerCancelContext();
      cppmempool::Retain(this->pServerCancelContext);
   }
   catch (exceptions::BaseException &e) {
      ReleaseBeforeThrowing();
      throw e;
   }
}

HttpServer::~HttpServer(void)
{
   ReleaseBeforeThrowing();
}

void HttpServer::ReleaseBeforeThrowing()
{
   if (pParams) {
      cppmempool::Release(pParams);
      pParams = NULL;
   }

   if (pSockList) {
      cppmempool::Release(pSockList);
      pSockList = NULL;
   }

   if (pSockArray) {
      cppmempool::Release(pSockArray);
      pSockArray = NULL;
   }

   if (pServerCancelContext) {
      cppmempool::Release(pServerCancelContext);
      pServerCancelContext = NULL;
   }
}

/**
 *  Start server -- start all sockets to listen
 */
void HttpServer::StartServer()
{
   if (pSockArray)
      return;

   const int DEFAULT_BACKLOG = 5;

   SocketFactory * pSocketFactory = static_cast<SocketFactory*>(const_cast<BaseObject*>(
                                                                 pParams->GetParam(HttpParams::P_SOCKETFACTORY)));
   if (!pSocketFactory)
      throw HttpException(_S("SocketFactory not present"), http::NoSocketFactoryError);

   // initialize listening sockets
   if (!pSocketFactory->CreateListeningSockets(*pSockList, DEFAULT_BACKLOG))
      throw HttpException(_S("Create listening socket(s) failed"), http::SocketError);

   // prepare an array to be used in GetIncomingClients (one more to hold the waiting socket)
   pSockArray = new ArrayCppObjT<Socket>(pSockList->GetCount() + 1);
   cppmempool::Retain(pSockArray);
}

/**
 *  Get number of listening sockets
 */
int HttpServer::GetListeningSocketCount()
{
   return static_cast<int>(pSockList->GetCount());
}

/**
 *  Get HttpServerEndPoint(s) to serve those incoming clients.
 *  This function is blocked until at least one client is connected.
 *  
 *  @param [out] endPointArray -- On return, contains HttpServerEndPoint(s) to serve the client(s).
 *                                The array size should be the same as GetListeningSocketCount()
 *
 *  @return number of available HttpServerEndPoint in the array
 */
int HttpServer::GetIncomingClients(ArrayCppObjT<HttpServerEndPoint> &endPointArray)
{
   if (!pSockArray)
      return 0;

   if (endPointArray.GetSize() != pSockList->GetCount())
      throw HttpException(_S("Array isn't the same size as the number of listening sockets"));

   const int WAITSOCK_SLOT = 0;

   pSockArray->Clear();
   endPointArray.Clear();

   // first slot is the waiting socket
   pSockArray->Set(WAITSOCK_SLOT, static_cast<ServerCancelContext*>(pServerCancelContext)->GetInternalSock());
   for (int i = 0 ; i < static_cast<int>(pSockList->GetCount()); ++i)
      pSockArray->Set(WAITSOCK_SLOT + 1 + i, pSockList->Get(i));

   // wait for pending sockets
   int nrSocket = Socket::ChkPendingSockets(*pSockArray, pParams->GetIntParam(HttpParams::P_SOTIMEOUT));
   
   if (nrSocket > 0 && pSockArray->Get(WAITSOCK_SLOT) != NULL)
      throw HttpException(_S("User has stopped this server"), http::OK);

   // Check available socket(s)
   for (int i = WAITSOCK_SLOT + 1, j = 0; i < static_cast<int>(pSockArray->GetSize()) && j < nrSocket; ++i) {
      Socket *pListenSock = pSockArray->Get(i);
      if (pListenSock) {
         // create a new endpoint
         HttpParams *pEndPointParams = new HttpParams();
         pEndPointParams->SetParam(HttpParams::P_ENDPOINTSOCKET, pListenSock->Accept());
         //pEndPointParams->SetParam(HttpParams::P_SOCKETFACTORY, const_cast<BaseObject*>(pParams->GetParam(HttpParams::P_SOCKETFACTORY)));
         HttpServerEndPoint *pServerEndPoint = new HttpServerEndPoint(pEndPointParams);
         endPointArray.Set(j++, pServerEndPoint);
      }
   }

   return nrSocket;
}

/**
 *  Get a context to stop this server from blocking at GetIncomingClient()
 */
ICancelContext * HttpServer::GetCancelContext()
{
   return pServerCancelContext;
}

} // end of namespace http

} // end of namespace net

} // end of namespace cppflib
